'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Imports JCsTools.JCQ.IcqInterface.DataTypes

Public Class IcqIconService
    Inherits BaseConnector
    Implements Interfaces.IIconService

    Private _IconId As Integer
    Private _UploadIconRequest As UploadIconRequest

    Public Sub New(ByVal context As IcqContext)
        MyBase.New(context)

        Dim connector As IcqConnector = TryCast(context.GetService(Of Interfaces.IConnector)(), IcqConnector)

        If connector Is Nothing Then Throw New InvalidCastException("Context Connector Service must be of Type IcqConnector")

        connector.RegisterSnacHandler(&H1, &H5, New Action(Of DataTypes.Snac0105)(AddressOf AnalyseSnac0105))
        connector.RegisterSnacHandler(&H1, &H21, New Action(Of DataTypes.Snac0121)(AddressOf AnalyseSnac0121))
        connector.RegisterSnacHandler(&H13, &H6, New Action(Of DataTypes.Snac1306)(AddressOf AnalyseSnac1306))
        connector.RegisterSnacHandler(&H13, &HE, New Action(Of DataTypes.Snac130E)(AddressOf AnalyseSnac130E))
    End Sub

#Region " Internal Action Processing "

    Private _IsProcessing As Boolean
    Private _IsAvailable As Boolean

    Private ReadOnly _Actions As New Queue(Of IAvatarServiceAction)

    Protected Event ServiceAvailable As EventHandler

    Protected Sub AddAction(ByVal action As IAvatarServiceAction)
        Core.Kernel.Logger.Log("IcqIconService", TraceEventType.Information, "Adding Action {0}", action)

        SyncLock _Actions
            _Actions.Enqueue(action)
        End SyncLock

        If Not IsConnected And Not _IsRequestingConnection Then
            RequestConnection()
        Else
            ProcessActions()
        End If
    End Sub

    Protected ReadOnly Property IsAvailable() As Boolean
        Get
            Return _IsAvailable
        End Get
    End Property

    Private Sub OnServiceAvailable(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ServiceAvailable
        Try
            _IsAvailable = True
            TcpContext.SetCloseUnexpected()

            ProcessActions()

            TcpContext.SetCloseExpected()
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Protected Sub ProcessActions()
        Dim action As IAvatarServiceAction

        If _IsProcessing Then Return
        _IsProcessing = True

        Try
            Core.Kernel.Logger.Log("IcqIconService", TraceEventType.Information, "Processing {0} Actions {1}", _Actions.Count, String.Join(";"c, (From x In _Actions Select Convert.ToString(x)).ToArray))

            Do
                SyncLock _Actions
                    If _Actions.Count > 0 Then action = _Actions.Dequeue Else action = Nothing
                End SyncLock

                If action IsNot Nothing Then action.Execute()
            Loop While action IsNot Nothing
        Finally
            _IsProcessing = False
        End Try
    End Sub

#End Region

#Region " Low Level I/O "
    Private _IsRequestingConnection As Boolean

    Private Sub RequestConnection()
        _IsRequestingConnection = True

        Dim iconServiceActivation As Snac0104

        iconServiceActivation = New Snac0104
        iconServiceActivation.ServiceFamilyId = &H10

        Dim transfer As IIcqDataTranferService = DirectCast(Context.GetService(Of Interfaces.IConnector)(), IIcqDataTranferService)

        transfer.Send(iconServiceActivation)
    End Sub

    Private Sub OnFlapReceived(ByVal sender As Object, ByVal e As IcqInterface.FlapTransportEventArgs) Handles Me.FlapReceived
        Try
            Dim infos As New List(Of String)

            For Each x As IcqInterface.DataTypes.ISerializable In e.Flap.DataItems
                infos.Add(CObj(x).ToString)
            Next

            Core.Kernel.Logger.Log("IcqIconService", TraceEventType.Information, "<< Seq: {0} Channel: {1} Items: {2}", e.Flap.DatagramSequenceNumber, e.Flap.Channel, String.Join(", ", infos.ToArray))
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub OnFlapSent(ByVal sender As Object, ByVal e As IcqInterface.FlapTransportEventArgs) Handles Me.FlapSent
        Try
            Dim infos As New List(Of String)

            For Each x As IcqInterface.DataTypes.ISerializable In e.Flap.DataItems
                infos.Add(CObj(x).ToString)
            Next

            Core.Kernel.Logger.Log("IcqIconService", TraceEventType.Information, ">> Seq: {0} Channel: {1} Items: {2}", e.Flap.DatagramSequenceNumber, e.Flap.Channel, String.Join(", ", infos.ToArray))
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub
#End Region

    Private Sub AnalyseSnac0105(ByVal dataIn As Snac0105)
        ' Server accepts the connection request for the "Icon Server".

        Try
            If IsConnected Then Return

            Dim parts() As String = dataIn.ServerAddress.ServerAddress.Split(":"c)

            Dim ip As System.Net.IPAddress = System.Net.IPAddress.Parse(parts(0))
            Dim port As Integer
            Dim endpoint As System.Net.IPEndPoint

            If parts.Length > 1 Then
                port = Integer.Parse(parts(1))
            Else
                port = 5190
            End If

            endpoint = New System.Net.IPEndPoint(ip, port)

            InnerConnect(endpoint)

            Me.RegisterSnacHandler(Of Snac0103)(&H1, &H3, New Action(Of Snac0103)(AddressOf AnalyseSnac0103))
            Me.RegisterSnacHandler(Of Snac0118)(&H1, &H18, New Action(Of Snac0118)(AddressOf AnalyseSnac0118))
            Me.RegisterSnacHandler(Of Snac0113)(&H1, &H13, New Action(Of Snac0113)(AddressOf AnalyseSnac0113))
            Me.RegisterSnacHandler(Of Snac0107)(&H1, &H7, New Action(Of Snac0107)(AddressOf AnalyseSnac0107))
            Me.RegisterSnacHandler(&H10, &H3, New Action(Of DataTypes.Snac1003)(AddressOf AnalyseSnac1003))
            Me.RegisterSnacHandler(Of Snac1005)(&H10, &H5, New Action(Of Snac1005)(AddressOf AnalyseSnac1005))

            Dim flap As FlapSendSignInCookie

            flap = New FlapSendSignInCookie
            flap.AuthorizationCookie.AuthorizationCookie.AddRange(dataIn.AuthorizationCookie.AuthorizationCookie)

            Send(flap)

            _IsRequestingConnection = False
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac0103(ByVal dataIn As Snac0103)
        ' Server aks to accept service family versions

        Dim requiredVersions As List(Of Integer)
        Dim notSupported As List(Of Integer)

        Try
            requiredVersions = New List(Of Integer)(New Integer() {&H1, &H10})
            notSupported = New List(Of Integer)

            For Each x As Integer In requiredVersions
                If Not dataIn.ServerSupportedFamilyIds.Contains(x) Then
                    notSupported.Add(x)
                End If
            Next

            If notSupported.Count = 0 Then
                Dim dataOut As Snac0117 = New Snac0117

                dataOut.FamilyNameVersionPairs.Add(New FamilyVersionPair(&H1, &H4))
                dataOut.FamilyNameVersionPairs.Add(New FamilyVersionPair(&H10, &H1))

                Send(dataOut)
            Else
                Throw New NotSupportedException("This server does not support all required features!")
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac0121(ByVal dataIn As Snac0121)
        ' Server sends an extended status request. If Type = 0x01 server requests an icon upload.

        Try
            Debug.WriteLine(String.Format("Extended Status Request: {0}", dataIn.Notification.Type), "IcqIconService")

            If _UploadIconRequest Is Nothing OrElse _UploadIconRequest.IsCompleted Then Return

            If dataIn.Notification.Type = ExtendedStatusNotificationType.UploadIconRequest Then
                Dim notification As UploadIconNotification

                notification = DirectCast(dataIn.Notification, UploadIconNotification)

                If notification.IconFlag = UploadIconFlag.FirstUpload Then
                    Debug.WriteLine("Icon upload requested.", "IcqIconService")

                    Dim action As UploadAvatarAction

                    action = New UploadAvatarAction(Me, _UploadIconRequest.IconData)

                    AddAction(action)
                Else
                    Debug.WriteLine("Icon available.", "IcqIconService")

                    _UploadIconRequest.IsCompleted = True

                    Context.Identity.SetIconHash(notification.IconHash)
                End If
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac1003(ByVal dataIn As Snac1003)
        ' server acknowledges icon upload.

        Try
            _UploadIconRequest.IsCompleted = True

            If dataIn.IconHash.Count > 0 Then
                Debug.WriteLine("Icon upload succeeded.", "IcqIconService")

                If _UploadIconRequest IsNot Nothing Then
                    Context.Identity.SetIconHash(New List(Of Byte)(_UploadIconRequest.IconMd5))
                End If
            Else
                Debug.WriteLine("Icon upload failed.", "IcqIconService")
            End If

            If _UploadIconRequest IsNot Nothing Then _UploadIconRequest.IsCompleted = True
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac0118(ByVal dataIn As Snac0118)
        Dim dataOut As Snac0106

        Try
            dataOut = New Snac0106

            Send(dataOut)
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac0113(ByVal dataIn As Snac0113)
        Throw New NotImplementedException
    End Sub

    Private Sub AnalyseSnac0107(ByVal dataIn As Snac0107)
        ' Server accepted the rate configuration.
        ' The connection now can be used.

        Dim serverRateGroupIds As List(Of Integer)
        Dim dataOut As Snac0108

        Try
            serverRateGroupIds = New List(Of Integer)

            For Each x As RateGroup In dataIn.RateGroups
                serverRateGroupIds.Add(x.GroupId)
            Next

            dataOut = New Snac0108

            dataOut.RateGroupIds.AddRange(serverRateGroupIds)

            Send(dataOut)

            RaiseEvent ServiceAvailable(Me, EventArgs.Empty)
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac1005(ByVal dataIn As Snac1005)
        ' Received Icon data.

        Dim c As Interfaces.IContact

        Try
            c = Context.GetService(Of Interfaces.IStorageService).GetContactByIdentifier(dataIn.Uin)

            If dataIn.IconData.Count > 0 Then
                Debug.WriteLine(String.Format("Received Icon for {0}.", c.Identifier), "IcqIconService")

                c.SetIconData(dataIn.IconData)
            Else
                Debug.WriteLine(String.Format("Receive Icon for {0} failed.", c.Identifier), "IcqIconService")
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac1306(ByVal dataIn As Snac1306)
        ' The Server sent the buddy list.
        ' Grab the Icon id to allow updates.

        Try
            If dataIn.BuddyIcon IsNot Nothing Then
                _IconId = dataIn.BuddyIcon.ItemId
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub AnalyseSnac130E(ByVal dataIn As Snac130E)
        ' Server akknowledges the icon upload request.
        ' It will ask the client to upload the icon with Snac 01,21.

        Try
            If _UploadIconRequest Is Nothing OrElse _UploadIconRequest.IsCompleted Then Return
            If _UploadIconRequest.RequestId <> dataIn.RequestId Then Return

            Dim code = dataIn.ActionResultCodes.FirstOrDefault

            If code = SSIActionResultCode.Success Then
                Debug.WriteLine("Icon upload request accepted.", "IcqIconService")
                _UploadIconRequest.IsAccepted = True
            Else
                Debug.WriteLine(String.Format("Icon upload request rejected: {0}.", code), "IcqIconService")
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Function GetSSIBudyIcon(ByVal request As UploadIconRequest) As SSIBuddyIcon
        Dim icon As SSIBuddyIcon

        icon = New SSIBuddyIcon
        icon.ItemId = _IconId
        icon.BuddyIcon.IconHash.AddRange(request.IconMd5)

        Return icon
    End Function

    Public Sub RequestContactIcon(ByVal contact As Interfaces.IContact) Implements Interfaces.IIconService.RequestContactIcon
        If contact.IconHash Is Nothing Then Return

        Dim action As RequestAvatarAction

        If (From x In _Actions Let y = TryCast(x, RequestAvatarAction) Where y IsNot Nothing AndAlso y.Contact.Identifier = contact.Identifier).Any Then Return

        action = New RequestAvatarAction(Me, contact)

        AddAction(action)
    End Sub

    Public Sub UploadIcon(ByVal avatar() As Byte) Implements Interfaces.IIconService.UploadIcon
        Dim newIcon As Snac1308
        Dim editIcon As Snac1309

        If _UploadIconRequest IsNot Nothing AndAlso Not _UploadIconRequest.IsCompleted Then Return

        _UploadIconRequest = New UploadIconRequest(avatar)

        Dim transfer As IIcqDataTranferService = DirectCast(Context.GetService(Of Interfaces.IConnector)(), IIcqDataTranferService)

        If _IconId > 0 Then
            editIcon = New Snac1309
            editIcon.BuddyIcon = GetSSIBudyIcon(_UploadIconRequest)
            transfer.Send(editIcon)
            _UploadIconRequest.RequestId = editIcon.RequestId
        Else
            newIcon = New Snac1308
            newIcon.BuddyIcon = GetSSIBudyIcon(_UploadIconRequest)
            transfer.Send(newIcon)
            _UploadIconRequest.RequestId = newIcon.RequestId
        End If
    End Sub
End Class

Public Class UploadIconRequest
    Public Sub New(ByVal data As Byte())
        _IconData = data

        Using cg = New System.Security.Cryptography.MD5CryptoServiceProvider
            Using ms As New System.IO.MemoryStream(IconData)
                _IconMd5 = cg.ComputeHash(ms)
            End Using
        End Using
    End Sub

    Private _IsCompleted As Boolean
    Public Property IsCompleted() As Boolean
        Get
            Return _IsCompleted
        End Get
        Set(ByVal value As Boolean)
            _IsCompleted = value
        End Set
    End Property

    Private _IsAccepted As Boolean
    Public Property IsAccepted() As Boolean
        Get
            Return _IsAccepted
        End Get
        Set(ByVal value As Boolean)
            _IsAccepted = value
        End Set
    End Property

    Private _RequestId As Long
    Public Property RequestId() As Long
        Get
            Return _RequestId
        End Get
        Set(ByVal value As Long)
            _RequestId = value
        End Set
    End Property

    Private ReadOnly _IconData As Byte()
    Public ReadOnly Property IconData() As Byte()
        Get
            Return _IconData
        End Get
    End Property

    Private ReadOnly _IconMd5 As Byte()
    Public ReadOnly Property IconMd5() As Byte()
        Get
            Return _IconMd5
        End Get
    End Property

End Class

Public Class UploadAvatarAction
    Implements IAvatarServiceAction

    Private ReadOnly _Service As IcqIconService
    Private ReadOnly _AvatarBytes As Byte()

    Private Shared ReferenceCounter As Integer

    Public Sub New(ByVal service As IcqIconService, ByVal avatar As Byte())
        _Service = service
        _AvatarBytes = avatar
    End Sub

    Public ReadOnly Property Service() As IcqIconService Implements IAvatarServiceAction.Service
        Get
            Return _Service
        End Get
    End Property

    Public Sub Execute() Implements IAvatarServiceAction.Execute

        Dim dataOut As Snac1002

        dataOut = New Snac1002
        dataOut.ReferenceNumber = Threading.Interlocked.Increment(ReferenceCounter)
        dataOut.IconData.AddRange(_AvatarBytes)

        Debug.WriteLine("Sending Icon to server.", "IcqIconService")

        Service.Send(dataOut)
    End Sub
End Class

Public Class RequestAvatarAction
    Implements IAvatarServiceAction

    Private ReadOnly _Service As IcqIconService
    Private ReadOnly _Contact As Interfaces.IContact

    Public Sub New(ByVal service As IcqIconService, ByVal contact As Interfaces.IContact)
        _Service = service
        _Contact = contact
    End Sub

    Public ReadOnly Property Contact() As Interfaces.IContact
        Get
            Return _Contact
        End Get
    End Property

    Public ReadOnly Property Service() As IcqIconService Implements IAvatarServiceAction.Service
        Get
            Return _Service
        End Get
    End Property

    Public Sub Execute() Implements IAvatarServiceAction.Execute
        If _Contact.IconHash Is Nothing Then Return

        Dim dataOut As DataTypes.Snac1004
        dataOut = New DataTypes.Snac1004
        dataOut.IconHash.AddRange(_Contact.IconHash)
        dataOut.Uin = _Contact.Identifier

        Debug.WriteLine(String.Format("Requesting Icon for {0}.", _Contact.Identifier), "IcqIconService")

        Service.Send(dataOut)
    End Sub

    Public Overrides Function ToString() As String
        Return String.Format("{0}: {1}", MyBase.ToString(), _Contact.Identifier)
    End Function
End Class

Public Interface IAvatarServiceAction
    ReadOnly Property Service() As IcqIconService

    Sub Execute()
End Interface